//GA mpi
//$Id: ga_mpi.h 441 2010-04-09 17:27:20Z Oleg.Bulychov $
#pragma once

#include "src/heo/include/lf_endl.h"
#include "src/heo/include/easy_mpi.h"
#include "src/heo/include/ring_coop.h"
#include "ga_common.h"
//__________________________________________________________________________________
/**
The Island Model with Ring Migration
*/
template<class T>
class GA_MPI:
    public GA<T>
{
public:
    typedef typename GA<T>::TEnvironment TEnvironment;
    typedef typename GA<T>::TSolution TSolution;

    typedef typename GA<T>::TDirection TDirection;
    typedef typename GA<T>::TCostType  TCostType;
protected:
    ring_cooperation<mpi::recv, mpi::send, mpi::isend> coop_;

public:

    template<class TInOutSolution>
    void solve(Solution_Pool<TInOutSolution>& pool, mpi::communicator& comm)
    {
        ASSERT_(this->rank_ == comm.rank());
        int np   = comm.size();
        int prev = (np + this->rank_ - 1) % np;
        int next = (this->rank_ + 1)      % np;

        this->init_From_Pool(pool);
        this->evaluate_Parents();
        this->update_Best_Fitness();
        this->calc_Max_Step();
        this->show_Progress(0);

        for (int step = 1; step <= this->max_step_; ++step)
        {
            this->select_Parents();

            this->apply_Intra_Operators(step);

            this->evaluate_Offsprings();

            this->select_Offsprings();
            
            this->update_Best_Fitness();

            this->show_Progress(step);

            if (this->stop(step))
            {
                if (1 == np || !this->coop_.get_Rate())
                    break;//:no coop - no need for 'pending stop'
                this->pending_stop_ = 1;
            }

            if (this->coop_.cooperate(*this, this->rank_, prev, next, np, step, comm))
            {
                if (this->coop_.is_Async_Mode())
                {
                    if (this->sent_stop_)
                        break;
                }
                else
                {
                    int stop = 0;
                    comm.all_reduce(this->pending_stop_, stop, MPI_SUM);
                    if (stop)
                    {
                        if (this->rank_ < this->env_->debug_level_)
                            std::cout << this->rank_ << ": stop reduced" << lf;
                        break;
                    }
                }
            }
        }
        this->sort_Population();

        this->clear_MPI(prev, comm);

        this->update_Pool(pool, comm, np);
    }

    template<class V>
    void accept(V& v)
    {
        v(static_cast<GA<T>&>(*this), "/");
        v(VAR_(coop));
    }
private:
    template<class TInOutSolution>
    void update_Pool(Solution_Pool<TInOutSolution>& pool, mpi::communicator& comm, int np)
    {
        if (0 == this->rank_)
        {
            pool.sol_list_.resize(this->output_size_);
            int j = 0;
            for (typename Solution_Pool<TInOutSolution>::iterator it = pool.sol_list_.begin();
                it != pool.sol_list_.end(); ++it, ++j)
            {
                it->init(this->env_, this->rnd_);
                it->assign(*(this->parent_[j]));
            }

            if (this->env_->debug_level_)
                std::cout << "0: cost = " << this->parent_[0]->get_Fitness() << lf;

            for (int r = 1; r < np; ++r)
            {
                for (int j = 0; j < this->output_size_; ++j)
                {
                    mpi::recv(r, comm) >> this->tmp_;

                    TCostType fit = this->tmp_.get_Fitness();
                    if (!j && this->env_->debug_level_)
                        std::cout << r << ": cost = " << fit << lf;
                    for (typename Solution_Pool<TInOutSolution>::iterator it = pool.sol_list_.begin();
                        it != pool.sol_list_.end(); ++it)
                    {
                        if (TDirection()(fit, it->get_Cost()))
                        {
                            pool.sol_list_.insert(it, 1, this->tmp_);
                            pool.sol_list_.pop_back();
                            break;
                        }
                    }
                }
            }
        }
        else
        {
            for (int j = 0; j < this->output_size_; ++j)
            {
                mpi::send(0, comm) << *(this->parent_[j]);
            }
        }
    }

    void clear_MPI(int prev, mpi::communicator& comm)
    {
        comm.barrier();
        if (!coop_.is_Async_Mode())
            return;
        //std::cout << rank << ":enter clear_MPI(...)" << std::endl;
        mpi::recv in(prev, comm);
        while (in.is_Data_Available(0))
        {
            //__debugbreak();
            //std::cout << rank << ":***zombi-migrant(s) killed" << std::endl;
            int stop_flag;
            in >> stop_flag;
            for (int i = 0; i < this->migration_size_; ++i)
            {
                in >> this->tmp_;
            }
        }
        comm.barrier();
        //std::cout << rank << ":leave clear_MPI(...)" << std::endl;
    }

};
//__________________________________________________________________________________
//__________________________________________________________________________________

//EOF!
